/**
 * 
 */
package Verifier.FiatShamirProofsVerification;

import java.util.Arrays;

import Verifier.GeneralUtils;
import Verifier.ArithmeticSystem.Field;
import Verifier.ArithmeticSystem.Group;
import Verifier.ArithmeticSystem.GroupElement;
import Verifier.ArithmeticSystem.ProductElement;
import Verifier.ByteTree.ByteTree;
import Verifier.ByteTree.Leaf;
import Verifier.Cryptography.HashFunction;
import Verifier.Cryptography.PRG;
import Verifier.Cryptography.RandomOracle;

/**
 * This class provides a container class to all the data a Fiat-Shamir proof needs.
 * It allows the changing of the data in the class without creating a new proof info object.
 * 
 * @author Moshe Baavur
 * 
 */
public class FiatShamirProofInfo {
	// all the parameters needed by the Fiat-Shamir Proofs
	private byte[] prefix;
	private int proverIndex;
	private int arraysSize;
	private int componentBits;
	private int acceptedError;
	private int challengeBits;
	private PRG vectorsRandomGenerator;
	private Group worldGroup;
	private Field worldField;
	private GroupElement[] pedersenCommitments;
	private byte[] proofCommitment;
	private byte[] proofReply;
	private ProductElement elGamalPublicKey; // (g, y)
	private Group ciphertextsGroup;
	private Group plaintextsGroup;
	private Field randomizerGroup;
	private ProductElement[] inputCiphertext;
	private ProductElement[] outputCiphertext;
	private GroupElement[] decryptionFactors;
	private GroupElement[] independentGenerators;
	private GroupElement partialPublicKey;
	private RandomOracle seedsRandomOracle;
	private RandomOracle challengesRandomOracle;

	private HashFunction usedHash;
	
	/**
	 * Creates an empty proof information object. To initialize the elements, one should use the init methods to replace the data saved in the elements.
	 */
	public FiatShamirProofInfo() {
		// set all the members to their initial values
		this.prefix = null;
		this.proverIndex = -1;
		this.arraysSize = 0;
		this.componentBits = 0;
		this.acceptedError = 0;
		this.challengeBits = 0;
		this.vectorsRandomGenerator = null;
		this.worldGroup = null;
		this.worldField = null;
		this.pedersenCommitments = null;
		this.proofCommitment = null;
		this.proofReply = null;
		this.elGamalPublicKey = null;
		this.inputCiphertext = null;
		this.outputCiphertext = null;
		this.partialPublicKey = null;
		this.seedsRandomOracle = null;
		this.challengesRandomOracle = null;
		this.decryptionFactors = null;
		this.independentGenerators = null;
		this.randomizerGroup = null;
		this.ciphertextsGroup = null;
		this.plaintextsGroup = null;
		
		this.usedHash = HashFunction.SHA256;
	}
	
	// init the random oracles and create the independent generators
	private void initRandomOracles(HashFunction hash) throws Exception {
		//create the random oracles
		this.seedsRandomOracle = new RandomOracle(hash, vectorsRandomGenerator.NumberOfBitsSeed());
		this.challengesRandomOracle = new RandomOracle(hash, challengeBits);

		// create the seed byte array for the independent generators
		Leaf l = new Leaf(GeneralUtils.convertStringToBytes("generators"), null);
		byte[] byteArray = ByteTree.byteStringToByteArray(l.toByteString());
		
		byte[] seedArray = new byte[byteArray.length + prefix.length];
		System.arraycopy(prefix, 0, seedArray, 0, prefix.length);
		System.arraycopy(byteArray, 0, seedArray, prefix.length, byteArray.length);
		
		// generate independent generators
		this.independentGenerators = (worldGroup.randomArray(arraysSize, vectorsRandomGenerator, seedsRandomOracle.OutputRandomOracle(seedArray), acceptedError));
	}

	/**
	 * Initializes the proof info with all the data needed for running the Shuffle Of Commitments Proof.
	 * 
	 * @param prefix the prefix for the proof.
	 * @param proverIndex the index of the prover. This data is mostly unused, but it doesn't harm to know.
	 * @param arraysSize the size of the arrays in the proof.
	 * @param componentBits the size of each component that need to be randomly chosen.
	 * @param acceptedError the number of bits which are accepted as an error when choosing a component randomly.
	 * @param challengeBits the number of bits in the challenge.
	 * @param vectorsRandomGenerator the PRG to use to randomly create vectors.
	 * @param worldGroup the world group which all the elements are related to.
	 * @param pedersenCommitments the array of pedersen commitments.
	 * @param proofCommitment the proof commitment, which is of the form of an array of bytes (in the proof it would interpret mostly as a byte tree).
	 * @param proofReply the proof reply, which is of the form of an array of bytes (in the proof it would interpret mostly as a byte tree).
	 * @param hash the hash to use in the proof.
	 * @throws Exception thrown when the independent generators cannot be created due to problem with the oracles.
	 */
	public void init(byte[] prefix, int proverIndex, int arraysSize,
			int componentBits, int acceptedError, int challengeBits,
			PRG vectorsRandomGenerator, Group worldGroup,
			GroupElement[] pedersenCommitments, byte[] proofCommitment,
			byte[] proofReply, HashFunction hash) throws Exception {
		// check if we should regenerate the RO
		boolean regenerateRO = !Arrays.equals(prefix, this.prefix) || 
				(null == vectorsRandomGenerator) || (null == this.vectorsRandomGenerator) ||
				(vectorsRandomGenerator.NumberOfBitsSeed() != this.vectorsRandomGenerator.NumberOfBitsSeed()) ||
				(usedHash != hash);
		
		// save the data into the instance fields
		this.prefix = prefix;
		this.proverIndex = proverIndex;
		this.arraysSize = arraysSize;
		this.componentBits = componentBits;
		this.acceptedError = acceptedError;
		this.challengeBits = challengeBits;
		this.vectorsRandomGenerator = vectorsRandomGenerator;
		if ((null == this.worldGroup) || (!this.worldGroup.equals(worldGroup))) {
			this.worldGroup = worldGroup;
			// create the world field
			this.worldField = new Field(this.worldGroup.getOrder());
		}
		this.pedersenCommitments = pedersenCommitments;
		this.proofCommitment = proofCommitment;
		this.proofReply = proofReply;
		this.randomizerGroup = null;
		this.ciphertextsGroup = null;
		this.plaintextsGroup = null;
		this.usedHash = hash;
		
		if (regenerateRO) {
			// create the needed random oracles
			initRandomOracles(hash);
		}
	}

	/**
	 * Initializes the proof info with all the data needed for running the Shuffle Of Ciphertexts Consistent Commitment Proof.
	 * 
	 * @param prefix the prefix for the proof.
	 * @param proverIndex the index of the prover. This data is mostly unused, but it doesn't harm to know.
	 * @param arraysSize the size of the arrays in the proof.
	 * @param componentBits the size of each component that need to be randomly chosen.
	 * @param acceptedError the number of bits which are accepted as an error when choosing a component randomly.
	 * @param challengeBits the number of bits in the challenge.
	 * @param vectorsRandomGenerator the PRG to use to randomly create vectors.
	 * @param worldGroup the world group which all the elements are related to.
	 * @param ciphertextsGroup the cipher texts group in the proof. It is noted as C in the algorithm.
	 * @param randomizerField the randomizers field in the proof. It is noted as R in the algorithm.
	 * @param pedersenCommitments the array of pedersen commitments.
	 * @param elGamalPublicKey the el gamal public key to use to encrypt the objects that are used in the proof.
	 * @param inputCiphertext the list of cipher texts that were the input for the server.
	 * @param outputCiphertext the list of cipher texts that were generated by the server.
	 * @param proofCommitment the proof commitment, which is of the form of an array of bytes (in the proof it would interpret mostly as a byte tree).
	 * @param proofReply the proof reply, which is of the form of an array of bytes (in the proof it would interpret mostly as a byte tree).
	 * @param hash the hash to use in the proof.
	 * @throws Exception thrown when the independent generators cannot be created due to problem with the oracles.
	 */
	public void init(byte[] prefix, int proverIndex, int arraysSize,
			int componentBits, int acceptedError, int challengeBits,
			PRG vectorsRandomGenerator, Group worldGroup, Group ciphertextsGroup, Field randomizerField,
			GroupElement[] pedersenCommitments, ProductElement elGamalPublicKey,
			ProductElement[] inputCiphertext, ProductElement[] outputCiphertext,
			byte[] proofCommitment, byte[] proofReply, HashFunction hash) throws Exception {
		// check if we should regenerate the RO
		boolean regenerateRO = !Arrays.equals(prefix, this.prefix) || 
				(null == vectorsRandomGenerator) || (null == this.vectorsRandomGenerator) ||
				(vectorsRandomGenerator.NumberOfBitsSeed() != this.vectorsRandomGenerator.NumberOfBitsSeed()) ||
				(usedHash != hash);
		
		// save the data into the instance fields
		this.prefix = prefix;
		this.proverIndex = proverIndex;
		this.arraysSize = arraysSize;
		this.componentBits = componentBits;
		this.acceptedError = acceptedError;
		this.challengeBits = challengeBits;
		this.vectorsRandomGenerator = vectorsRandomGenerator;
		if ((null == this.worldGroup) || (!this.worldGroup.equals(worldGroup))) {
			this.worldGroup = worldGroup;
			// create the world field
			this.worldField = new Field(this.worldGroup.getOrder());
		}
		this.pedersenCommitments = pedersenCommitments;
		this.proofCommitment = proofCommitment;
		this.proofReply = proofReply;
		this.elGamalPublicKey = elGamalPublicKey;
		this.inputCiphertext = inputCiphertext;
		this.outputCiphertext = outputCiphertext;
		this.randomizerGroup = randomizerField;
		this.ciphertextsGroup = ciphertextsGroup;
		this.plaintextsGroup = null;
		this.usedHash = hash;
		
		if (regenerateRO) {
			// create the needed random oracles
			initRandomOracles(hash);
		}
	}

	/**
	 * Initializes the proof info with all the data needed for running the Correct Decryption Factors Proof.
	 * 
	 * @param prefix the prefix for the proof.
	 * @param proverIndex the index of the prover. This data is mostly unused, but it doesn't harm to know.
	 * @param arraysSize the size of the arrays in the proof.
	 * @param componentBits the size of each component that need to be randomly chosen.
	 * @param acceptedError the number of bits which are accepted as an error when choosing a component randomly.
	 * @param challengeBits the number of bits in the challenge.
	 * @param vectorsRandomGenerator the PRG to use to randomly create vectors.
	 * @param worldGroup the world group which all the elements are related to.
	 * @param plaintextsGroup the plain texts group in the proof. It is noted as M in the algorithm.
	 * @param partialPublicKey the partial public key to use to decrypt the elements in the proof.
	 * @param inputCiphertext the cipher texts that were need to be decrypted.
	 * @param decryptionFactors the factors that are used to decrypt the elements.
	 * @param proofCommitment the proof commitment, which is of the form of an array of bytes (in the proof it would interpret mostly as a byte tree).
	 * @param proofReply the proof reply, which is of the form of an array of bytes (in the proof it would interpret mostly as a byte tree).
	 * @param hash the hash to use in the proof.
	 * @throws Exception thrown when the independent generators cannot be created due to problem with the oracles.
	 */
	public void init(byte[] prefix, int proverIndex, int arraysSize,
			int componentBits, int acceptedError, int challengeBits,
			PRG vectorsRandomGenerator, Group worldGroup, Group plaintextsGroup,
			GroupElement partialPublicKey, ProductElement[] inputCiphertext,
			GroupElement[] decryptionFactors, byte[] proofCommitment,
			byte[] proofReply, HashFunction hash) throws Exception {
		// check if we should regenerate the RO
		boolean regenerateRO = !Arrays.equals(prefix, this.prefix) || 
				(null == vectorsRandomGenerator) || (null == this.vectorsRandomGenerator) ||
				(vectorsRandomGenerator.NumberOfBitsSeed() != this.vectorsRandomGenerator.NumberOfBitsSeed()) ||
				(usedHash != hash);
		
		// save the data into the instance fields
		this.prefix = prefix;
		this.proverIndex = proverIndex;
		this.arraysSize = arraysSize;
		this.componentBits = componentBits;
		this.acceptedError = acceptedError;
		this.challengeBits = challengeBits;
		this.vectorsRandomGenerator = vectorsRandomGenerator;
		if ((null == this.worldGroup) || (!this.worldGroup.equals(worldGroup))) {
			this.worldGroup = worldGroup;
			// create the world field
			this.worldField = new Field(this.worldGroup.getOrder());
		}
		this.proofCommitment = proofCommitment;
		this.proofReply = proofReply;
		this.inputCiphertext = inputCiphertext;
		this.decryptionFactors = decryptionFactors;
		this.partialPublicKey = partialPublicKey;
		this.randomizerGroup = null;
		this.ciphertextsGroup = null;
		this.plaintextsGroup = plaintextsGroup;
		this.usedHash = hash;
		
		if (regenerateRO) {
			// create the needed random oracles
			initRandomOracles(hash);
		}
	}

	/**
	 * Returns the prefix that was saved in the last call to init.
	 * 
	 * @return the array of bytes which is the prefix for the proof.
	 */
	public byte[] prefix() {
		return (prefix);
	}

	/**
	 * Returns the size of the arrays in the proof that was saved in the last call to init.
	 * 
	 * @return the size of the arrays in the proof.
	 */
	public int arraysSize() {
		return (arraysSize);
	}

	/**
	 * Returns the size of the arrays in the proof that was saved in the last call to init.
	 * 
	 * @return the size of the arrays in the proof.
	 */
	public int proverIndex() {
		return (proverIndex);
	}

	/**
	 * Returns the number of bits to generate to create a component in 
	 * the proof that was saved in the last call to init.
	 * 
	 * @return the number of bits for a component.
	 */
	public int componentBits() {
		return (componentBits);
	}

	/**
	 * Returns the number of bits needed in the challenge that was saved in the last call to init.
	 * 
	 * @return the number of bits for the challenge.
	 */
	public int challengeBits() {
		return (challengeBits);
	}

	/**
	 * Returns the number of bits which are accepted as an error when randomizing 
	 * components in the proof that was saved in the last call to init.
	 * 
	 * @return the number of bits accepted as an error.
	 */
	public int acceptedError() {
		return (acceptedError);
	}

	/**
	 * Returns the PRG to use in the proof that was saved in the last call to init.
	 * 
	 * @return the PRG.
	 */
	public PRG vectorsRandomGenerator() {
		return (vectorsRandomGenerator);
	}

	/**
	 * Returns the world group of elements in the proof that was saved in the last call to init.
	 * 
	 * @return the world group.
	 */
	public Group worldGroup() {
		return (worldGroup);
	}
	
	/**
	 * Returns the world field of elements in the proof that is created from the world group.
	 * 
	 * @return the world group.
	 */
	public Field worldField() {
		return (worldField);
	}

	/**
	 * Returns the pedersen commitments in the proof that was saved in the last call to init.
	 * 
	 * @return the pedersen commitments in the proof.
	 */
	public GroupElement[] pedersenCommitments() {
		return (pedersenCommitments);
	}

	/**
	 * Returns the commitment in the proof that was saved in the last call to init.
	 * 
	 * @return the commitment in the proof.
	 */
	public byte[] proofCommitment() {
		return (proofCommitment);
	}

	/**
	 * Returns the reply in the proof that was saved in the last call to init.
	 * 
	 * @return the reply in the proof.
	 */
	public byte[] proofReply() {
		return (proofReply);
	}
	
	/**
	 * Returns the group of plain texts in the proof. It is noted by M in the verifier's document.
	 * 
	 * @return the group of plain texts.
	 */
	public Group plaintextsGroup() {
		return (plaintextsGroup);
	}
	
	/**
	 * Returns the group of cipher texts in the proof. It is noted by C in the verifier's document.
	 * 
	 * @return the group of cipher texts.
	 */
	public Group ciphertextsGroup() {
		return (ciphertextsGroup);
	}

	/**
	 * Returns the group of randomizers in the proof. It is noted by R in the verifier's document.
	 * 
	 * @return the group of randomizers.
	 */
	public Field randomizerGroup() {
		return (randomizerGroup);
	}

	/**
	 * Returns the el gamal key to use for encryption in the proof that was 
	 * saved in the last call to init.
	 * 
	 * @return the el gamal key to use in the proof.
	 */
	public ProductElement elGamalPublicKey() {
		return (elGamalPublicKey);
	}
	
	/**
	 * Returns the cipher texts that were as input to the server in the proof 
	 * that was saved in the last call to init.
	 * 
	 * @return the input cipher texts for the server in the proof.
	 */
	public ProductElement[] inputCiphertext() {
		return (inputCiphertext);
	}

	/**
	 * Returns the cipher texts that were as output to the server in the proof 
	 * that was saved in the last call to init.
	 * 
	 * @return the output cipher texts for the server in the proof.
	 */
	public ProductElement[] outputCiphertext() {
		return (outputCiphertext);
	}
	
	/**
	 * Returns the decryption factors to use in the proof that was saved in the last call to init.
	 * 
	 * @return the decryption factors to use in the proof.
	 */
	public GroupElement[] decryptionFactors() {
		return (decryptionFactors);
	}

	/**
	 * Returns the partial public key for decryption in the proof that was saved in the last call to init.
	 * 
	 * @return the partial public key for decryption in the proof.
	 */
	public GroupElement partialPublicKey() {
		return (partialPublicKey);
	}
	
	/**
	 * Returns the seeds random generator. It is noted by RO_s in the verifier's document.
	 * 
	 * @return the seeds random generator.
	 */
	public RandomOracle seedsRandomOracle() {
		return (seedsRandomOracle);
	}
	
	/**
	 * Returns the challenges random generator. It is noted by RO_v in the verifier's document.
	 * 
	 * @return the seeds random generator.
	 */
	public RandomOracle challengesRandomOracle() {
		return (challengesRandomOracle);
	}
	
	/**
	 * Returns the array of independent generators. It is noted by h in the verifier's document.
	 * 
	 * @return the array of independent generators.
	 */
	public GroupElement[] independentGenerators() {
		return (independentGenerators);
	}
}